package cz.muni.fi.xvano1.trinity.supplementary;

import cz.muni.fi.xvano1.trinity.core.Core;
import cz.muni.fi.xvano1.trinity.data.LicenseRequest;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.Integer;
import java.lang.Integer;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Class containing methods that are necessary to work with virtual calendar
 * throughout the application.
 *
 * @author Jan Vano
 */
public class CalendarHandler {

    /**
     * Method for accessing information about names of the months in year.
     *
     * @return Map of Integer (position) to String (name) of the months in year.
     */
    public static Map<Integer, String> getMonthNames() {
        Map<Integer, String> months = new HashMap<Integer, String>();
        months.put(1, "January");
        months.put(2, "February");
        months.put(3, "March");
        months.put(4, "April");
        months.put(5, "May");
        months.put(6, "June");
        months.put(7, "July");
        months.put(8, "August");
        months.put(9, "September");
        months.put(10, "October");
        months.put(11, "November");
        months.put(12, "December");
        return months;
    }

    public static List<String> getStamps() {
        List<String> result = new ArrayList<String>();
        result.add("00:00-00:29");
        result.add("00:30-00:59");
        result.add("01:00-01:29");
        result.add("01:30-01:59");
        result.add("02:00-02:29");
        result.add("02:30-02:59");
        result.add("03:00-03:29");
        result.add("03:30-03:59");
        result.add("04:00-04:29");
        result.add("04:30-04:59");
        result.add("05:00-05:29");
        result.add("05:30-05:59");
        result.add("06:00-06:29");
        result.add("06:30-06:59");
        result.add("07:00-07:29");
        result.add("07:30-07:59");
        result.add("08:00-08:29");
        result.add("08:30-08:59");
        result.add("09:00-09:29");
        result.add("09:30-09:59");
        result.add("10:00-10:29");
        result.add("10:30-10:59");
        result.add("11:00-11:29");
        result.add("11:30-11:59");
        result.add("12:00-12:29");
        result.add("12:30-12:59");
        result.add("13:00-13:29");
        result.add("13:30-13:59");
        result.add("14:00-14:29");
        result.add("14:30-14:59");
        result.add("15:00-15:29");
        result.add("15:30-15:59");
        result.add("16:00-16:29");
        result.add("16:30-16:59");
        result.add("17:00-17:29");
        result.add("17:30-17:59");
        result.add("18:00-18:29");
        result.add("18:30-18:59");
        result.add("19:00-19:29");
        result.add("19:30-19:59");
        result.add("20:00-20:29");
        result.add("20:30-20:59");
        result.add("21:00-21:29");
        result.add("21:30-21:59");
        result.add("22:00-22:29");
        result.add("22:30-22:59");
        result.add("23:00-23:29");
        result.add("23:30-23:59");
        
        return result;
    }

    /**
     * Method computes list of days in specifick week in a year.
     *
     * @param year Year provided
     * @param week Week provided
     * @return List of all dates of week in a year in a YYYYMMDD format
     */
    public static List<String> getDays(int year, int week) {
        List<String> response = new ArrayList<String>();
        String yearString;
        String monthString;
        String dayString;
        String date;
        int month;
        int day;
        Calendar cal = Calendar.getInstance(new SimpleTimeZone(0, "Europe/Prague"), new Locale("cs"));
        cal.set(java.util.Calendar.YEAR, year);
        cal.set(java.util.Calendar.WEEK_OF_YEAR, week);

        //MONDAY
        cal.set(java.util.Calendar.DAY_OF_WEEK, java.util.Calendar.MONDAY);
        month = cal.get(java.util.Calendar.MONTH);
        day = cal.get(java.util.Calendar.DATE);
        yearString =  String.valueOf(cal.get(java.util.Calendar.YEAR));
        if (month < 10) {
            monthString = "0" + String.valueOf(month + 1);
        } else {
            monthString = String.valueOf(month + 1);
        }
        if (day < 10) {
            dayString = "0" + String.valueOf(day);
        } else {
            dayString = String.valueOf(day);
        }
        date = yearString + monthString + dayString;
        response.add(date);
        //TUESDAY
        cal.set(java.util.Calendar.DAY_OF_WEEK, java.util.Calendar.TUESDAY);
        month = cal.get(java.util.Calendar.MONTH);
        day = cal.get(java.util.Calendar.DATE);
        yearString =  String.valueOf(cal.get(java.util.Calendar.YEAR));
        if (month < 10) {
            monthString = "0" + String.valueOf(month + 1);
        } else {
            monthString = String.valueOf(month + 1);
        }
        if (day < 10) {
            dayString = "0" + String.valueOf(day);
        } else {
            dayString = String.valueOf(day);
        }
        date = yearString + monthString + dayString;
        response.add(date);
        //WEDNESDAY
        cal.set(java.util.Calendar.DAY_OF_WEEK, java.util.Calendar.WEDNESDAY);
        month = cal.get(java.util.Calendar.MONTH);
        day = cal.get(java.util.Calendar.DATE);
        yearString =  String.valueOf(cal.get(java.util.Calendar.YEAR));
        if (month < 10) {
            monthString = "0" + String.valueOf(month + 1);
        } else {
            monthString = String.valueOf(month + 1);
        }
        if (day < 10) {
            dayString = "0" + String.valueOf(day);
        } else {
            dayString = String.valueOf(day);
        }
        date = yearString + monthString + dayString;
        response.add(date);
        //THURSDAY
        cal.set(java.util.Calendar.DAY_OF_WEEK, java.util.Calendar.THURSDAY);
        month = cal.get(java.util.Calendar.MONTH);
        day = cal.get(java.util.Calendar.DATE);
        yearString =  String.valueOf(cal.get(java.util.Calendar.YEAR));
        if (month < 10) {
            monthString = "0" + String.valueOf(month + 1);
        } else {
            monthString = String.valueOf(month + 1);
        }
        if (day < 10) {
            dayString = "0" + String.valueOf(day);
        } else {
            dayString = String.valueOf(day);
        }
        date = yearString + monthString + dayString;
        response.add(date);
        //FRIDAY
        cal.set(java.util.Calendar.DAY_OF_WEEK, java.util.Calendar.FRIDAY);
        month = cal.get(java.util.Calendar.MONTH);
        day = cal.get(java.util.Calendar.DATE);
        yearString =  String.valueOf(cal.get(java.util.Calendar.YEAR));
        if (month < 10) {
            monthString = "0" + String.valueOf(month + 1);
        } else {
            monthString = String.valueOf(month + 1);
        }
        if (day < 10) {
            dayString = "0" + String.valueOf(day);
        } else {
            dayString = String.valueOf(day);
        }
        date = yearString + monthString + dayString;
        response.add(date);
        //SATURDAY
        cal.set(java.util.Calendar.DAY_OF_WEEK, java.util.Calendar.SATURDAY);
        month = cal.get(java.util.Calendar.MONTH);
        day = cal.get(java.util.Calendar.DATE);
        yearString =  String.valueOf(cal.get(java.util.Calendar.YEAR));
        if (month < 10) {
            monthString = "0" + String.valueOf(month + 1);
        } else {
            monthString = String.valueOf(month + 1);
        }
        if (day < 10) {
            dayString = "0" + String.valueOf(day);
        } else {
            dayString = String.valueOf(day);
        }
        date = yearString + monthString + dayString;
        response.add(date);
        //SUNDAY
        cal.set(java.util.Calendar.DAY_OF_WEEK, java.util.Calendar.SUNDAY);
        month = cal.get(java.util.Calendar.MONTH);
        day = cal.get(java.util.Calendar.DATE);
        yearString =  String.valueOf(cal.get(java.util.Calendar.YEAR));
        if (month < 10) {
            monthString = "0" + String.valueOf(month + 1);
        } else {
            monthString = String.valueOf(month + 1);
        }
        if (day < 10) {
            dayString = "0" + String.valueOf(day);
        } else {
            dayString = String.valueOf(day);
        }
        date = yearString + monthString + dayString;
        response.add(date);
        return response;
    }

    /**
     * Simple method for finding which day in a month is last.
     *
     * @param year Year
     * @param month Month
     * @return Last day of a month in specified year.
     */
    public static int getLastDayInMonth(int year, int month) {
        Calendar calz = Calendar.getInstance();
        calz.set(year, month - 1, 1);
        return calz.getActualMaximum(Calendar.DAY_OF_MONTH);
    }

    /**
     * This method creates structure which is used in application front end.
     * Based on the year and month, it creates Map of Maps. The first dimension
     * Map has key 0 which maps to a list of other keys in this dimension. Other
     * keys have values of weeks and map to lists of days. Structure is based on
     * czech locale and time.
     *
     * @param year Year of target structure
     * @param month Month of target structure
     * @return Created structure as described
     */
    public static Map<Integer, List<Integer>> getMonthArray(int year, int month) {
        System.out.println("Getting instance of calendar for " + year + ", month " + month);
        Calendar cal = Calendar.getInstance(new SimpleTimeZone(0, "Europe/Prague"), new Locale("cs"));
        boolean preWeek = false;
        boolean postWeek = false;
        cal.set(year, month - 1, 1);
        System.out.println("The first week of month is " + cal.get(Calendar.WEEK_OF_MONTH));
        if (cal.get(Calendar.WEEK_OF_MONTH) == 0) {
            preWeek = true;
        }
        cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
        //System.out.println("Maximum day is " + cal.getActualMaximum(Calendar.DAY_OF_MONTH));
        //System.out.println("The last week of month is " + cal.get(Calendar.WEEK_OF_MONTH));
        if (cal.get(Calendar.WEEK_OF_MONTH) == 5) {
            postWeek = true;
        }
        Map<Integer, List<Integer>> monthArray = new HashMap<Integer, List<Integer>>();
        int from = 1;
        if (preWeek) {
            from = 0;
        }
        int to = 4;
        if (postWeek) {
            to = 5;
        }

        List<Integer> weeks = new ArrayList<Integer>();
        for (int i = from; i <= to; i++) {
            //System.out.println("Setting for week " + i + " of month");
            cal.set(Calendar.WEEK_OF_MONTH, i);
            List<Integer> list = new ArrayList<Integer>(7);
            cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
            list.add(cal.get(Calendar.DATE));
//            System.out.println(cal.get(Calendar.DATE));
            cal.set(Calendar.DAY_OF_WEEK, Calendar.TUESDAY);
            list.add(cal.get(Calendar.DATE));
//            System.out.println(cal.get(Calendar.DATE));
            cal.set(Calendar.DAY_OF_WEEK, Calendar.WEDNESDAY);
            list.add(cal.get(Calendar.DATE));
//            System.out.println(cal.get(Calendar.DATE));
            cal.set(Calendar.DAY_OF_WEEK, Calendar.THURSDAY);
            list.add(cal.get(Calendar.DATE));
//            System.out.println(cal.get(Calendar.DATE));
            cal.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY);
            list.add(cal.get(Calendar.DATE));
//            System.out.println(cal.get(Calendar.DATE));
            cal.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);
            list.add(cal.get(Calendar.DATE));
//            System.out.println(cal.get(Calendar.DATE));
            cal.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
            list.add(cal.get(Calendar.DATE));
//            System.out.println(cal.get(Calendar.DATE));
            monthArray.put(cal.get(Calendar.WEEK_OF_YEAR), list);
            weeks.add(cal.get(Calendar.WEEK_OF_YEAR));
        }
        monthArray.put(0, weeks);



//        System.out.println(cal.getTime());
        return monthArray;
    }

    /**
     * Simmilar to getMonthArray() this method creates one dimension above yet
     * two dimensional maps of months and embed them into one three dimensional
     * structure
     *
     * @param year of target structure
     * @return Structure where a number is mapped to respective month
     */
    public static Map<Integer, Map<Integer, List<Integer>>> getYearArray(int year) {
        Map<Integer, Map<Integer, List<Integer>>> yearArray = new HashMap<Integer, Map<Integer, List<Integer>>>();
        for (int i = 1; i <= 12; i++) {
            yearArray.put(i, getMonthArray(year, i));
        }
        return yearArray;
    }

    /**
     * This method works with internal structure from Lmcal (thesis of Lukas
     * Jane) which was modified to provide output. It expects plain text in
     * format A;B;C;D per line where A=date in YYYYMMDD format, B=start of
     * reservation in seconds from Epoch, B=end of event in seconds from Epoch,
     * D=representation of request in specialized format. Start-end can span
     * across multiple days. After the file is parsed, information are stored
     * into Map of String to Integer, where String represents date:
     * YYYYMMDD;toolbox or date-time: YYYYMMDDT;toolbox - T is time stamp. Time
     * stamp values can be from 1 to 48 where one day is divided into 30minute
     * parts: stamp 1 is 00:00 - 00:29 etc. Integer which keys are mapped to
     * represent count of licenses used in a time stamp and/or day.
     *
     * @param input BufferedReader which is opened to Lmcal output file.
     * @return System-wide structure of all reservations
     */
    public synchronized static Map<String, Integer> getCalendarStructure(BufferedReader input) {
        if (input == null) {
            return null;
        }
        Map<String, Integer> calendar = new HashMap<String, Integer>();
        String line;
        String[] lineData;
        try {
            while ((line = input.readLine()) != null) {
//                System.out.println(line);
                lineData = line.split(";");
                String dayString = lineData[0];
                if (dayString.length() != 8) {
                    return null;
                }
                int year = Integer.parseInt(dayString.substring(0, 4));
                int month = Integer.parseInt(dayString.substring(4, 6));
                int day = Integer.parseInt(dayString.substring(6, 8));
                Calendar calStart = Calendar.getInstance();
                Calendar calEnd = Calendar.getInstance();
                calStart.set(year, month - 1, day, 0, 0, 0);
                calEnd.set(year, month - 1, day, 23, 59, 59);
                long startOneL = calStart.getTimeInMillis();
                long endOneL = calEnd.getTimeInMillis();
                long startTwoL = Long.parseLong(lineData[1]) * 1000;
                long endTwoL = Long.parseLong(lineData[2]) * 1000;
                long start = (startOneL > startTwoL) ? startOneL : startTwoL;
                long end = (endOneL < endTwoL) ? endOneL : endTwoL;
                if ((end - start) <= 0) {
                    System.out.println(startOneL);
                    System.out.println(calStart.getTime());
                    System.out.println(endOneL);
                    System.out.println(calEnd.getTime());
                    System.out.println(startTwoL);
                    Date d1 = new Date(startTwoL);
                    System.out.println(d1);
                    System.out.println(endTwoL);
                    Date d2 = new Date(endTwoL);
                    System.out.println(d2);
                    System.out.println(start);
                    System.out.println(end);
                    return null;
                }
                Calendar startCalendar = Calendar.getInstance();
                startCalendar.setTimeInMillis(start);
                Calendar endCalendar = Calendar.getInstance();
                endCalendar.setTimeInMillis(end);
                int startTimeStamp = (startCalendar.get(Calendar.HOUR_OF_DAY) * 2) + ((startCalendar.get(Calendar.MINUTE) / 30) + 1);
                int endTimeStamp = (endCalendar.get(Calendar.HOUR_OF_DAY) * 2) + ((endCalendar.get(Calendar.MINUTE) / 30) + 1);
//                System.out.println(startCalendar.getTime());
//                System.out.println((startCalendar.get(Calendar.HOUR_OF_DAY)*2));
//                System.out.println(((startCalendar.get(Calendar.MINUTE) / 30) + 1));
//                System.out.println(endCalendar.getTime());
//                System.out.println((endCalendar.get(Calendar.HOUR_OF_DAY)*2));
//                System.out.println(((endCalendar.get(Calendar.MINUTE) / 30) + 1));
//                System.out.println(startTimeStamp + "   " + endTimeStamp);
                List<Integer> timeStamps = new ArrayList<Integer>(48);
                for (int i = startTimeStamp; i <= endTimeStamp; i++) {
                    timeStamps.add(i);
                }
                Map<String, Integer> request = LicenseRequest.parseString(lineData[3]);
                Set<String> requestKeys = request.keySet();
                for (String toolbox : requestKeys) {
                    if (!timeStamps.isEmpty()) {
                        Integer base = calendar.get(dayString + ";" + toolbox);
                        if (base == null) {
                            base = 0;
                        }
                        Integer addition = request.get(toolbox);
                        if (addition == null) {
                            addition = 0;
                        }
                        Integer newValue = base + addition;
                        if (newValue > 0) {
                            calendar.put(dayString + ";" + toolbox, newValue);
                        }
                    }
                    for (Integer i : timeStamps) {
                        Integer base = calendar.get(dayString + i + ";" + toolbox);
                        if (base == null) {
                            base = 0;
                        }
                        Integer addition = request.get(toolbox);
                        if (addition == null) {
                            addition = 0;
                        }
                        Integer newValue = base + addition;
                        if (newValue > 0) {
                            calendar.put(dayString + i + ";" + toolbox, newValue);
                        }
                    }
                }
            }
        } catch (IOException ex) {
            Logger.getLogger(CalendarHandler.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        } catch (NumberFormatException ex) {
            Logger.getLogger(CalendarHandler.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }

        return calendar;
    }

    /**
     * Method for reading output from Lmcal if reservations could not be
     * ammended. When user commit request to system the request is validated and
     * passed to Lmcal. If Lmcal detect that this request cannot be fulfilled
     * due to license limit, it sends negative response in a data file. This
     * method reads thad file and passes information to front end.
     *
     * @return Array of strings describing the negative response
     */
    public static String[] getNegativeResponse() {
        BufferedReader r;
        String line = null;
        try {
            r = new BufferedReader(new FileReader(Core.negativeResponse));
            line = r.readLine();
        } catch (IOException ex) {
            Logger.getLogger(CalendarHandler.class.getName()).log(Level.SEVERE, null, ex);
        }
        if (line == null) {
            String[] badResult = {"Negative response could not be read. Please refer to calendar views."};
            return badResult;
        }
        return line.split(";");
    }

    /**
     * Simple method which genrated standard java Calendar object according to
     * date-time input
     *
     * @param dateTime Date-time value in DD.MM.YYYY HH:MM
     * @return Java Calendar intepretation of input
     */
    public static Calendar getCalendarForDateTime(String dateTime) {
        if (!dateTime.matches(Core.dateRegex + " " + Core.timeRegex)) {
            return null;
        }
        int year = Integer.parseInt(dateTime.substring(6, 10));
        int month = Integer.parseInt(dateTime.substring(3, 5)) - 1;
        int day = Integer.parseInt(dateTime.substring(0, 2));
        int hour = Integer.parseInt(dateTime.substring(11, 13));
        int minute = Integer.parseInt(dateTime.substring(14, 16));
        Calendar calendar = new GregorianCalendar(year, month, day, hour, minute, 0);
        return calendar;
    }
}
